package br.org.isvi.interpretador.portugol;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
import java.util.ArrayList;

import br.org.isvi.interpretador.portugol.exception.SemanticErrorException;
import br.org.isvi.interpretador.portugol.operations.AndResolver;
import br.org.isvi.interpretador.portugol.operations.AssignResolver;
import br.org.isvi.interpretador.portugol.operations.BiggerResolver;
import br.org.isvi.interpretador.portugol.operations.DivResolver;
import br.org.isvi.interpretador.portugol.operations.EqualsResolver;
import br.org.isvi.interpretador.portugol.operations.MinusResolver;
import br.org.isvi.interpretador.portugol.operations.MultResolver;
import br.org.isvi.interpretador.portugol.operations.NotResolver;
import br.org.isvi.interpretador.portugol.operations.OrResolver;
import br.org.isvi.interpretador.portugol.operations.PotencyResolver;
import br.org.isvi.interpretador.portugol.operations.SmallerResolver;
import br.org.isvi.interpretador.portugol.operations.SumResolver;
import br.org.isvi.interpretador.portugol.operations.UtilsValidator;
import br.org.isvi.interpretador.portugol.operations.XOrResolver;
import br.org.isvi.interpretador.portugol.sablecc.analysis.DepthFirstAdapter;
import br.org.isvi.interpretador.portugol.sablecc.lexer.Lexer;
import br.org.isvi.interpretador.portugol.sablecc.lexer.LexerException;
import br.org.isvi.interpretador.portugol.sablecc.node.AAndExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.AAssignmentStatement;
import br.org.isvi.interpretador.portugol.sablecc.node.ABiggerExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.ABooleanExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.AConditionElseConditionComplement;
import br.org.isvi.interpretador.portugol.sablecc.node.AConditionLoop;
import br.org.isvi.interpretador.portugol.sablecc.node.AConditionTestConditionNormal;
import br.org.isvi.interpretador.portugol.sablecc.node.ADivTerm;
import br.org.isvi.interpretador.portugol.sablecc.node.AEqualsExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.AEscrevaStatement;
import br.org.isvi.interpretador.portugol.sablecc.node.AEscrevalnStatement;
import br.org.isvi.interpretador.portugol.sablecc.node.AIdentifierFactor;
import br.org.isvi.interpretador.portugol.sablecc.node.AInteiroType;
import br.org.isvi.interpretador.portugol.sablecc.node.ALeiaExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.ALogicoType;
import br.org.isvi.interpretador.portugol.sablecc.node.AMinusSumMinus;
import br.org.isvi.interpretador.portugol.sablecc.node.AMultTerm;
import br.org.isvi.interpretador.portugol.sablecc.node.ANotExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.ANumberFactor;
import br.org.isvi.interpretador.portugol.sablecc.node.AOrExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.APlusSumMinus;
import br.org.isvi.interpretador.portugol.sablecc.node.APowPotency;
import br.org.isvi.interpretador.portugol.sablecc.node.ARealType;
import br.org.isvi.interpretador.portugol.sablecc.node.ASimpleCondition;
import br.org.isvi.interpretador.portugol.sablecc.node.ASingleIdentifierList;
import br.org.isvi.interpretador.portugol.sablecc.node.ASingleStatementSequence;
import br.org.isvi.interpretador.portugol.sablecc.node.ASingleVariablesDefinitionList;
import br.org.isvi.interpretador.portugol.sablecc.node.ASmallerExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.AStringFactor;
import br.org.isvi.interpretador.portugol.sablecc.node.ATextoType;
import br.org.isvi.interpretador.portugol.sablecc.node.AVariablesDeclaration;
import br.org.isvi.interpretador.portugol.sablecc.node.AVariablesDefinition;
import br.org.isvi.interpretador.portugol.sablecc.node.AXorExpression;
import br.org.isvi.interpretador.portugol.sablecc.node.Node;
import br.org.isvi.interpretador.portugol.sablecc.node.PType;
import br.org.isvi.interpretador.portugol.sablecc.node.Start;
import br.org.isvi.interpretador.portugol.sablecc.node.TIdentifier;
import br.org.isvi.interpretador.portugol.sablecc.node.TSemicolon;
import br.org.isvi.interpretador.portugol.sablecc.node.Token;
import br.org.isvi.interpretador.portugol.sablecc.parser.Parser;
import br.org.isvi.interpretador.portugol.sablecc.parser.ParserException;
import br.org.isvi.interpretador.portugol.types.Kind;
import br.org.isvi.interpretador.portugol.types.Symbol;
import br.org.isvi.interpretador.portugol.types.VariableType;

public class SabbleInterpreter extends DepthFirstAdapter {

	ArrayList<String> varslist = new ArrayList<String>();
	ArrayList<Boolean> conditions = new ArrayList<Boolean>();
	boolean operation = false;
	boolean def = false;
	int line, pos;
	
	/**
	 * Controle do leia por causa dos tipos.
	 */
	boolean leiaStatment = false;
	
	/**
	 * Controle do assign.
	 */
	boolean assign = false;
	
	/**************************************************************
	 * 
	 * DEPURACAO
	 * 
	 **************************************************************/
	
	@Override
	public void inAVariablesDefinition(AVariablesDefinition node) {
		if(!canExecute()) 
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
	}

	@Override
	public void inASingleStatementSequence(ASingleStatementSequence node) {
		if(!canExecute()) 
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
	}

	/**************************************************************
	 * 
	 * DECLARACAO
	 * 
	 **************************************************************/
	@Override
	public void inAVariablesDeclaration(AVariablesDeclaration node) {
		def = true;
	}

	@Override
	public void outAVariablesDeclaration(AVariablesDeclaration node) {
		def = false;
	}

	@Override
	public void caseTIdentifier(TIdentifier node) {
		String name;

		name = node.getText();

		if (def) {
			varslist.add(name);
		}
	}
	
	@Override
	public void inATextoType(ATextoType node) {
		inType(node);
	}

	@Override
	public void inARealType(ARealType node) {
		inType(node);
	}

	@Override
	public void inALogicoType(ALogicoType node) {
		inType(node);
	}

	@Override
	public void inAInteiroType(AInteiroType node) {
		inType(node);
	}

	public void inType(PType node) {
		if (leiaStatment)// TODO olhar isso!
			return;

		for (String name : varslist) {
			InterpreterResolver.instance.declare(SymbolsFactory.createVariable(
					name, getVariableTypeFor(node)));
		}

		varslist = new ArrayList<String>();// REINICIA LISTAGEM
	}

	@Override
	public void inALeiaExpression(ALeiaExpression node) {
		leiaStatment = true;
	}

	@Override
	public void outALeiaExpression(ALeiaExpression node) {
		leiaStatment = false;

		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
//		if (assign) {// so joga no stack se tiver alguem para receber da erro no caso de um se
			Kind k = getInstaceKindFor(node.getType());
			InterpreterResolver.instance
					.addToStack(InterpreterResolver.instance.leia(k));
//		}
	}

	@Override
	public void inAEscrevalnStatement(AEscrevalnStatement node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
	}
	
	@Override
	public void outAEscrevalnStatement(AEscrevalnStatement node) {
		if(!canExecute())
			return;
		
//		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.escrevaNL(InterpreterResolver.instance
				.getOPerationResult());
	}

	@Override
	public void inAEscrevaStatement(AEscrevaStatement node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
	}
	
	@Override
	public void outAEscrevaStatement(AEscrevaStatement node) {
		
		if(!canExecute())
			return;
		
//		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.escreva(InterpreterResolver.instance
				.getOPerationResult());
	}

	@Override
	public void inAIdentifierFactor(AIdentifierFactor node) {
		if(!canExecute())
			return;
		
		String name = node.getIdentifier().getText();

		InterpreterResolver.instance.addToStack(InterpreterResolver.instance
				.getVariable(name));

	}

	@Override
	public void inANumberFactor(ANumberFactor node) {
		if(!canExecute())
			return;
		
		String symb = node.getNumber().getText();

		InterpreterResolver.instance.addToStack(SymbolsFactory
				.createNumberSymbol(symb));

	}

	@Override
	public void inAStringFactor(AStringFactor node) {
		if(!canExecute())
			return;
		
		String symb = node.getString().getText();

		InterpreterResolver.instance.addToStack(SymbolsFactory
				.createStringSymbol(symb));
	}

	@Override
	public void inABooleanExpression(ABooleanExpression node) {
		if(!canExecute())
			return;
		
		String symb = node.getBoolean().getText();

		InterpreterResolver.instance.addToStack(SymbolsFactory
				.createBooleanSymbol(symb));
	}

	@Override
	public void inAAssignmentStatement(AAssignmentStatement node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		assign = true;
		if (node.getIdentifier() != null) {
			InterpreterResolver.instance
					.addToStack(InterpreterResolver.instance.getVariable(node
							.getIdentifier().getText()));

			// DAR UMA LIMPA NO STACK
		}
	}

	@Override
	public void outAAssignmentStatement(AAssignmentStatement node) {
		if(!canExecute())
			return;
		
		assign = false;
		InterpreterResolver.instance.endOPeration(new AssignResolver());
	}

	/**************************************************
	 * ARITIMETICA
	 **************************************************/

	@Override
	public void outAMultTerm(AMultTerm node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new MultResolver());

	}

	@Override
	public void outADivTerm(ADivTerm node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new DivResolver());
	}
	
	@Override
	public void outAMinusSumMinus(AMinusSumMinus node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new MinusResolver());

	}

	@Override
	public void outAPlusSumMinus(APlusSumMinus node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new SumResolver());

	}

	@Override
	public void outAPowPotency(APowPotency node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new PotencyResolver());
	}

	/**************************************************
	 * LOGICA
	 **************************************************/
	
	@Override
	public void outABiggerExpression(ABiggerExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		InterpreterResolver.instance.endOPeration(new BiggerResolver());
	}

	@Override
	public void outASmallerExpression(ASmallerExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new SmallerResolver());
	}
	
	@Override
	public void outAEqualsExpression(AEqualsExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new EqualsResolver());
	} 
	
	@Override
	public void outAAndExpression(AAndExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new AndResolver());
	}
	
	
	@Override
	public void outAOrExpression(AOrExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new OrResolver());
	}
	
	@Override
	public void outAXorExpression(AXorExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		
		InterpreterResolver.instance.endOPeration(new XOrResolver());
	}
	
	@Override
	public void outANotExpression(ANotExpression node) {
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.toString());
		InterpreterResolver.instance.endOPeration(new NotResolver());
	}
	
	/**************************************************
	 * OUTROS
	 **************************************************/
	
	@Override
	public void caseTSemicolon(TSemicolon node) {//TODO FUNCIONA?
		if(!canExecute())
			return;
		
		if (operation) {
			InterpreterResolver.instance.getOPerationResult();
			operation = false;
		}
	}

	@Override
	public void defaultCase(Node node) {
		if (node instanceof Token) {
			Token t = (Token) node;
			pos = t.getPos();
			line = t.getLine();
		}
	}

	public int getLastLine() {
		return line;
	}

	public int getLastPos() {
		return pos;
	}

	
	/**********************************************************************
	 * CONDITIONAL
	 **********************************************************************/
	boolean inConditional = false;
	boolean inConditionalTest = false;
	
	/**
	 * Verifica se esta dentro de uma condicional e se pode executar.
	 * 
	 * @return
	 */
	private boolean canExecute() {
		boolean ret = true;
		
		if(conditions.size() > 0) {
			ret = conditions.get(conditions.size()-1);
		}
		
		return ret;
	}
	
	@Override
	public void inAConditionElseConditionComplement(
			AConditionElseConditionComplement node) {
		
		if(conditions.size() > 1) //eh um nested se ?
		{
			int pos = conditions.size()-1;
			
			if(conditions.get(pos-1)) {//se o pai não esta desbloqueado
				
				if(!conditions.get(pos))
					InterpreterResolver.instance.updateTarget(node.toString());
				
				conditions.set(pos, !conditions.get(pos));
				
			}
				
		} else {
			int pos = conditions.size()-1;
			
			if(!conditions.get(pos))
				InterpreterResolver.instance.updateTarget(node.toString());
			
			conditions.set(pos, !conditions.get(pos));
		}
	}
	
	@Override
	public void outASimpleCondition(ASimpleCondition node) {
		inConditional = false;		
		
		conditions.remove(conditions.size()-1);
	}
	
	@Override
	public void inAConditionTestConditionNormal(
			AConditionTestConditionNormal node) {
		
		
		if(!canExecute()) {
			conditions.add(false);
		} else {
			InterpreterResolver.instance.updateTarget(node.toString());
			inConditionalTest = true;
		}
		
	}
	
	@Override
	public void outAConditionTestConditionNormal(
			AConditionTestConditionNormal node) {
		boolean conditionalResult = false;
		inConditionalTest = false;
		
		if(!canExecute()) 
			return;
		
		Symbol sb = InterpreterResolver.instance.popStack();
		conditionalResult = (Boolean)UtilsValidator.getRealVariableValue(sb).getValue();
		
		conditions.add(conditionalResult);//AKI INDICO QUE ESTAMOS DENTRO DE UMA CONDICIONAL
	}
	
	/**********************************************************************
	 * LOOP
	 **********************************************************************/	
	
	@Override
	public void inAConditionLoop(AConditionLoop node) {
		
		if(!canExecute())
			return;
		
		InterpreterResolver.instance.updateTarget(node.getLoopTest().toString());
		
		node.getLoopTest().apply(this);
		Symbol sb = InterpreterResolver.instance.popStack();
		
		if((Boolean)UtilsValidator.getRealVariableValue(sb).getValue()) {	
			node.getStatementSequence().apply(this);

			node.apply(this);
		}
		
		conditions.add(false);
	}
	
	@Override
	public void outAConditionLoop(AConditionLoop node) {
		conditions.remove(conditions.size()-1);
	}
	
	/**
	 * Metodo Main.
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		SabbleInterpreter interpreter = new SabbleInterpreter();
		String fileName = "teste.por";
		
		if(args.length > 0)
			fileName = args[0];
			
		File file = new File(fileName);
	
		try {
			interpreter.execute(new FileReader(file), new BasicResolver());
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}


	/**
	 * Executa a interpretação.
	 */
	public void execute(Reader reader, Resolver resolver) {
		InterpreterResolver.instance.setResolver(resolver);
		Lexer lex = null;
		Parser parser = null;
		try {
			Start tree;

			PushbackReader pushbackReader = new PushbackReader(reader, 1024);

			lex = new Lexer(pushbackReader);
			parser = new Parser(lex);

			tree = parser.parse();
			tree.apply(this);

		} catch (SemanticErrorException e) {
			InterpreterResolver.instance.performeHandlingError(e.getError(),
					this.getLastLine(), this.getLastPos());
		} catch (ParserException e) {
			String msg = e.getMessage().replaceAll("expecting:", "esperando:");
			InterpreterResolver.instance.performeHandlingError(msg, e
					.getToken().getLine(), e.getToken().getPos());
		} catch (LexerException e) {
			String msg = "Na [linha, coluna] = "
					+ e.getMessage().replaceAll("Unknown token",
							"forma inválida");
			InterpreterResolver.instance.performeHandlingError(msg, -1, -1);
		} catch (IOException e) {
			InterpreterResolver.instance.performeHandlingError(
					"Arquivo não existe!", 0, 0);
		} catch (Throwable e) {
			InterpreterResolver.instance.performeHandlingError(
					"Erro desconhecido! " + e.getCause(), 0, 0);
			e.printStackTrace();
		}
	}
	
	static VariableType getVariableTypeFor(PType type) {
		VariableType ret = null;

		if (type instanceof AInteiroType)
			ret = VariableType.inteiro;

		if (type instanceof ATextoType)
			ret = VariableType.literal;

		if (type instanceof ALogicoType)
			ret = VariableType.booleano;

		if (type instanceof ARealType)
			ret = VariableType.real;

		return ret;
	}

	static Kind getInstaceKindFor(PType type) {
		Kind ret = null;

		if (type instanceof AInteiroType)
			ret = Kind.INTEIRO;

		if (type instanceof ATextoType)
			ret = Kind.TEXTO;

		if (type instanceof ALogicoType)
			ret = Kind.BOOLEANO;

		if (type instanceof ARealType)
			ret = Kind.REAL;

		return ret;
	}
}
